/*
 * Copyright (c) 2012 Universidad Antonio de Nebrija.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Apache License v2.0
 * which accompanies this distribution, and is available at
 * http://www.apache.org/licenses/LICENSE-2.0.html
 * 
 * Contributors:
 *    Universidad Antonio de Nebrija - initial API, implementation and documentation
 */
package com.nebrija.riif.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

/**
 * Superclass for the constant, fail_mode and parameter tokens 
 * @author asanchep
 *
 */
public class RIIFProperties implements RIIFElement{

	/** Internal key used to store the value of a constant/parameter */
	public static final String VALUE_KEY = "VALUE";
	/** Name of the rate property, widely used in the RIIF models*/
	public static final String RATE_KEY = "rate";
	/** Internal key used to store the type of a constant/parameter */
	public static final String TYPE_KEY = "TYPE";
	/** Name of the unit property, widely used in the RIIF models*/
	public static final String UNIT="unit";
	/** suffix added to a property to store the calculated value */
	public static final String CALCULATED_SUFFIX = "#calculated";
	
	/** Element that contains this RIIFProperties object*/
	protected RIIFElement container = null;
	/** name of the object */
	protected String name = "";
	/** properties stored in the object (value, type and others assigned to it)*/
	protected HashMap<String,Object> props = new HashMap<String,Object>();
	
	/** Map with the name of the property as key and the assertions over that key as value */
	protected HashMap<String,ArrayList<AssertValidation>> assertions = 
									new HashMap<String,ArrayList<AssertValidation>>();
	
	/**
	 * Creates a RIIFProperties object
	 * @param name the name of the object
	 */
	public RIIFProperties(String name){
		this.name=name;
	}
	
	@Override
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Getter for the properties stored in the object (value, type and others assigned to it)
	 * @return the property values indexed by their names
	 */
	public HashMap<String,Object> getProps() {
		return props;
	}

	/**
	 * Setter for the properties stored in the object (value, type and others assigned to it)
	 * @param props a hashmap with the property values indexed by their names
	 */
	public void setProps(HashMap<String,Object> props) {
		this.props = props;
	}

	/**
	 * Returns one of the properties stored in this object
	 * @param name the property name to be retrieved
	 * @return the value of this property
	 */
	public Object getProperty (String name){
		return props.get(name);
	}

	/**
	 * Set a property in this object. If one with the same name exists, it is replaced.
	 * @param name the name of the property
	 * @param value the value of the property
	 */
	public void setProperty (String name, Object value){
		props.put(name,value);
	}
	
	/**
	 * Gets the value of a constant or parameter.
	 * It is equivalent to getProperty(VALUE_KEY) 
	 * @return the value of the constant or parameter
	 */
	public Object getValue(){
		return props.get(VALUE_KEY);
	}
	
	/**
	 * Sets the value of a constant or parameter.
	 * It is equivalent to setProperty(VALUE_KEY, value) 
	 * @param value the value of the constant or parameter
	 */
	public void setValue(Object value){
		props.put(VALUE_KEY,value);
	}


	@Override
	public String toString() {
		StringBuffer str = new StringBuffer (name);
		str.append("\n");
		Set<String> keys = props.keySet();
		for (String key: keys){
			str.append("\t->").append(key)
				.append(":").append(props.get(key)).append("\n");
		}
		
		keys = assertions.keySet();
		for (String key: keys){
			str.append("\t->").append(key);
			str.append("\n");
			ArrayList<AssertValidation> vals = assertions.get(key);
			for (int i=0; i<vals.size();i++){
				str.append("\t").append(vals.get(i)).append("\n");
			}
		}		
		return str.toString();
	}
	
	/**
	 * Sets the container where this object resides.
	 * @param container the component/environment/... where it resides
	 */
	public void setParent (RIIFElement container){
		this.container = container;
	}
	
	/**
	 * Gets the container where this object resides.
	 * @return container the component/environment/... where it resides
	 */
	public RIIFElement getParent(){
		return this.container;
	}
	
	/**
	 * Adds an assertion related to a specific property
	 * @param validation the object that models the assertion
	 */
	public void addAssertion(AssertValidation validation){
		// Check if the validation target corresponds to this RIIFPRoperties element
		// Otherwise it is not added
		if(validation.getTarget()==null || validation.getTarget()!=this){
			return;
		}
		// Check if the property exists in the object
		// Otherwise it is not added
		String property = validation.getTargetProperty();
		
		if (props.get(property)==null){
			return;
		}
		
		// For every property we have an arraylist of assertions
		//TODO: Build a model for assertions affecting more than one property
		ArrayList<AssertValidation> propertyAssertions = assertions.get(property);
		// If it is the first assertion over a property, creates the arraylist
		if (propertyAssertions==null){
			propertyAssertions = new ArrayList<AssertValidation>();
		}
		// Add the assertion if it was not previously added
		if(!propertyAssertions.contains(validation)){
			propertyAssertions.add(validation);
		}
		// Only needed if the arraylist was created
		assertions.put(property, propertyAssertions);
	}

	@Override
	public void resolve(RIIFElement container) {
		// only valid if the container is a component
		if(container==null || !(container instanceof RIIFPropertiesContainer)){
			container=this.getParent();
		}

		// Retrieve all the properties
		Set<String> keys = props.keySet();
		HashMap<String,Object> calculated = new HashMap<String,Object>();
		// Calculate the values for every property
		for (String key: keys){
			Object value = props.get(key);
			// If it is not a string, it does not need to be calculated
			if (!(value instanceof String)){
				continue;
			}
			//There are other places where it can be computed.
			if(props.get(key+CALCULATED_SUFFIX)!=null){
				continue;
			}
			// Calculate the value
			Object calc = Util.resolveFormula((String)value, (RIIFPropertiesContainer) container);
			// If anything was done, fill the calculated property
			if(!value.equals(calc)){
				calculated.put(key + CALCULATED_SUFFIX, calc);
			}
		}
		// Add the calculated properties to the map.
		props.putAll(calculated);
		
		// Resolve the assertions
		keys = assertions.keySet();
		for (String key: keys){
			ArrayList<AssertValidation> vals = assertions.get(key);
			for (int i=0; i<vals.size();i++){
				// propagate to the assertion object
				vals.get(i).resolve(container);
			}
		}				
	}
	
	@Override
	public void clear(RIIFElement container) {
		// only valid if the container is a component
		if(container==null || !(container instanceof RIIFPropertiesContainer)){
			container=this.getParent();
		}
		// Retrieve all the properties
		Set<String> keys = props.keySet();

		// Check which keys need to be removed
		ArrayList<String> clearKeys = new ArrayList<String>();
		for (String key: keys){
			// search for those ending with the calculated suffix
			if(!key.endsWith(CALCULATED_SUFFIX)){
				continue;
			}
			clearKeys.add(key);
			
		}
		// Remove the calculated keys
		for(String key: clearKeys){
			props.remove(key);
		}
		
		// Same for assertion
		keys = assertions.keySet();
		for (String key: keys){
			ArrayList<AssertValidation> vals = assertions.get(key);
			for (int i=0; i<vals.size();i++){
				// Propagate the clear action to every assetion
				vals.get(i).clear(container);
			}
		}				
	}
	
	/**
	 * Getter for the list of assertions over a property	
	 * @param propertyName the name the assertions relate to
	 * @return the list of assertions over that property or null if no assertions exist
	 */
	public ArrayList<AssertValidation> getAssertions(String propertyName){
		return assertions.get(propertyName);
	}

	@Override
	public int getChildCount() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public RIIFElement getElement(String name) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Set<String> getElementNames() {
		// TODO Auto-generated method stub
		return null;
	}
}
